// Package server provides HTTP server functionality for the libvirt exporter.
package server

import (
	"context"
	"fmt"
	"html/template"
	"net/http"
	"regexp"
	"time"

	"gitee.com/openeuler/uos-libvirtd-exporter/collector"
	"gitee.com/openeuler/uos-libvirtd-exporter/slogx"
	"gitee.com/openeuler/uos-libvirtd-exporter/version"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/collectors"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Config defines the server configuration interface.
type Config interface {
	GetListenAddr() string      // Address to listen on, e.g. ":9102"
	GetMetricsPath() string     // Path for metrics endpoint, e.g. "/metrics"
	ExposeRuntimeMetrics() bool // Whether to expose Go and process metrics
}

// HealthChecker defines the interface for custom health checks.
type HealthChecker interface {
	IsHealthy() bool
	IsReady() bool // readiness check for external dependencies
}

// Server represents the HTTP server exposing Prometheus metrics.
type Server struct {
	config           Config
	libvirtCollector *collector.LibvirtCollector
	httpServer       *http.Server
	healthChecker    HealthChecker
	startTime        time.Time
}

// NewServer creates a new HTTP server instance.
func NewServer(cfg Config, libvirtCollector *collector.LibvirtCollector) *Server {
	return &Server{
		config:           cfg,
		libvirtCollector: libvirtCollector,
		startTime:        time.Now(),
	}
}

// SetupRouter configures and returns the HTTP handler mux.
func (s *Server) SetupRouter() http.Handler {
	mux := http.NewServeMux()

	// Create registry
	registry := prometheus.NewRegistry()
	registry.MustRegister(s.libvirtCollector)

	// Optionally register Go runtime/process metrics
	if s.config.ExposeRuntimeMetrics() {
		registry.MustRegister(
			collectors.NewGoCollector(),
			collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
		)
	}

	// Serve static files (CSS, etc.)
	fileServer := http.FileServer(http.Dir("./templates/"))
	mux.Handle("/styles.css", http.StripPrefix("/", fileServer))

	// Metrics endpoint
	mux.Handle(
		s.config.GetMetricsPath(),
		promhttp.HandlerFor(registry, promhttp.HandlerOpts{
			EnableOpenMetrics: true,
		}),
	)

	// Info endpoints
	mux.HandleFunc("/", s.handleRoot)
	mux.HandleFunc("/health", s.handleHealth)
	mux.HandleFunc("/ready", s.handleReady)

	slogx.Debug("HTTP routes configured",
		"metrics_path", s.config.GetMetricsPath(),
		"runtime_metrics_enabled", s.config.ExposeRuntimeMetrics())

	return mux
}

// handleRoot serves the root information page.
func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
	slogx.Debug("Handling root request", "remote_addr", r.RemoteAddr)

	// Get template name from query parameter or use default
	templateName := r.URL.Query().Get("template")
	if templateName == "" {
		templateName = "root.html"
	} else {
		// Security check - only allow alphanumeric, dash and dot characters
		matched, _ := regexp.MatchString(`^[a-zA-Z0-9\-_\.]+\.html$`, templateName)
		if !matched {
			templateName = "root.html"
		}
	}

	// Parse the template file
	tmpl, err := template.ParseFiles("templates/" + templateName)
	if err != nil {
		// Fallback to default template if specified template not found
		slogx.Warn("Failed to parse template, falling back to default",
			"template", templateName,
			"error", err)
		tmpl, err = template.ParseFiles("templates/root.html")
		if err != nil {
			http.Error(w, "Failed to load template", http.StatusInternalServerError)
			slogx.Error("Failed to parse default template", "error", err)
			return
		}
	}

	// Prepare data for the template
	data := struct {
		MetricsPath  string
		Version      string
		BuildTime    string
		Uptime       string
		TemplateName string
		LibvirtURI   string
		TotalVMs     int
		ActiveVMs    int
	}{
		MetricsPath:  s.config.GetMetricsPath(),
		Version:      version.Version,
		BuildTime:    version.BuildTime,
		Uptime:       time.Since(s.startTime).Truncate(time.Second).String(),
		TemplateName: templateName,
		LibvirtURI:   "N/A",
		TotalVMs:     0,
		ActiveVMs:    0,
	}

	// Try to get libvirt connection info and VM stats
	if s.libvirtCollector != nil {
		// Get libvirt URI from collector
		data.LibvirtURI = s.libvirtCollector.GetURI()

		// Get domain counts (total and active)
		totalVMs, activeVMs, err := s.libvirtCollector.GetDomainCounts()
		if err == nil {
			data.TotalVMs = totalVMs
			data.ActiveVMs = activeVMs
		} else {
			slogx.Warn("Failed to get domain counts", "error", err)
		}
	}

	// Execute the template
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := tmpl.Execute(w, data); err != nil {
		slogx.Error("Failed to execute template", "error", err)
	}
}

// handleHealth performs a basic liveness check.
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
	slogx.Debug("Handling health check request", "remote_addr", r.RemoteAddr)

	status := http.StatusOK
	message := "OK"

	if s.healthChecker != nil && !s.healthChecker.IsHealthy() {
		status = http.StatusServiceUnavailable
		message = "Service Unhealthy"
		slogx.Warn("Health check failed - service unhealthy",
			"remote_addr", r.RemoteAddr)
	} else {
		slogx.Debug("Health check passed",
			"remote_addr", r.RemoteAddr,
			"status", message)
	}

	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.WriteHeader(status)
	_, _ = w.Write([]byte(message))
}

// handleReady performs a readiness check (for dependencies like libvirtd connection).
func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) {
	slogx.Debug("Handling readiness check request", "remote_addr", r.RemoteAddr)

	status := http.StatusOK
	message := "Ready"

	if s.healthChecker != nil && !s.healthChecker.IsReady() {
		status = http.StatusServiceUnavailable
		message = "Not Ready"
		slogx.Warn("Readiness check failed - service not ready",
			"remote_addr", r.RemoteAddr)
	} else {
		slogx.Debug("Readiness check passed",
			"remote_addr", r.RemoteAddr,
			"status", message)
	}

	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.WriteHeader(status)
	_, _ = w.Write([]byte(message))
}

// SetHealthChecker attaches a custom health checker.
func (s *Server) SetHealthChecker(checker HealthChecker) {
	s.healthChecker = checker
}

// Start begins serving HTTP requests. It blocks until context cancellation or server error.
func (s *Server) Start(ctx context.Context) error {
	handler := s.SetupRouter()

	s.httpServer = &http.Server{
		Addr:         s.config.GetListenAddr(),
		Handler:      handler,
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 15 * time.Second,
		IdleTimeout:  30 * time.Second,
	}

	slogx.Info("Starting HTTP server",
		"listen_address", s.config.GetListenAddr(),
		"read_timeout", "5s",
		"write_timeout", "15s",
		"idle_timeout", "30s")

	// Start server in background
	errCh := make(chan error, 1)
	go func() {
		if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			errCh <- fmt.Errorf("http server error: %w", err)
		}
	}()

	select {
	case <-ctx.Done():
		slogx.Info("Shutdown signal received, stopping HTTP server")
		return s.Stop(context.Background())
	case err := <-errCh:
		return err
	}
}

// Stop gracefully shuts down the HTTP server.
func (s *Server) Stop(ctx context.Context) error {
	if s.httpServer == nil {
		return nil
	}
	shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()
	slogx.Info("Shutting down HTTP server",
		"shutdown_timeout", "5s")
	return s.httpServer.Shutdown(shutdownCtx)
}
